﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ordinapoche.Core
{
    public delegate void Step ();
    public delegate void AnimationDone ();
    public delegate void Animation (AnimationDone aDone);

    /// <summary>
    ///     The implementation of a single Ordinapoche processor core.
    /// </summary>

    public class Processor
    {
        private Memory _Memory;
        private Animator _Animator = new Animator ();
        private Step _CurStep;
        private Step _NextStep;
        private bool _Executing;
        private bool _Ready;
        private int _ClockMultiplier = 1;

        private int _InstructionRegister;
        private int _ProgramCounter;
        private int _MemoryPointer;
        private int _Accumulator;
        private int _TempValue;
        private int _AluResult;

        private OpCode _OpCode;

        public Processor (Memory aMemory)
        {
            _Memory = aMemory;
        }


        #region Accumulator

        public event EventHandler<RegisterChangeEventArgs> AccumulatorChanging;
        public event EventHandler<RegisterChangeEventArgs> AccumulatorChanged;

        protected virtual void OnAccumulatorChanging (RegisterChangeEventArgs e)
        {
            if (AccumulatorChanging != null)
            {
                AccumulatorChanging (this, e);
            }
        }

        protected virtual void OnAccumulatorChanged (RegisterChangeEventArgs e)
        {
            if (AccumulatorChanged != null)
            {
                AccumulatorChanged (this, e);
            }
        }

        public int Accumulator
        {
            get
            {
                return _Accumulator;
            }
            set
            {
                int v = value % 10000;

                if (_Accumulator != v)
                {
                    RegisterChangeEventArgs e = new RegisterChangeEventArgs (_Accumulator, v);
                    OnAccumulatorChanging (e);
                    _Accumulator = v;
                    OnAccumulatorChanged (e);
                }
            }
        }

        #endregion

        #region Program Counter

        public event EventHandler<RegisterChangeEventArgs> ProgramCounterChanging;
        public event EventHandler<RegisterChangeEventArgs> ProgramCounterChanged;

        protected virtual void OnProgramCounterChanging (RegisterChangeEventArgs e)
        {
            if (ProgramCounterChanging != null)
            {
                ProgramCounterChanging (this, e);
            }
        }

        protected virtual void OnProgramCounterChanged (RegisterChangeEventArgs e)
        {
            if (ProgramCounterChanged != null)
            {
                ProgramCounterChanged (this, e);
            }
        }

        /// <summary>
        ///     The Program Counter, that points to the next instruction
        ///     to be executed.
        /// </summary>

        public int ProgramCounter
        {
            get
            {
                return _ProgramCounter;
            }
            set
            {
                int v = value % 100;

                if (v < 0)
                {
                    v += 100;
                }

                if (v != _ProgramCounter)
                {
                    RegisterChangeEventArgs e = new RegisterChangeEventArgs (_ProgramCounter, v);
                    OnProgramCounterChanging (e);
                    _ProgramCounter = v;
                    OnProgramCounterChanged (e);
                }
            }
        }

        #endregion

        #region Instruction Register

        public event EventHandler<RegisterChangeEventArgs> InstructionRegisterChanging;
        public event EventHandler<RegisterChangeEventArgs> InstructionRegisterChanged;

        protected virtual void OnInstructionRegisterChanging (RegisterChangeEventArgs e)
        {
            if (InstructionRegisterChanging != null)
            {
                InstructionRegisterChanging (this, e);
            }
        }

        protected virtual void OnInstructionRegisterChanged (RegisterChangeEventArgs e)
        {
            if (InstructionRegisterChanged != null)
            {
                InstructionRegisterChanged (this, e);
            }
        }

        public int InstructionRegister
        {
            get
            {
                return _InstructionRegister;
            }
            set
            {
                int v = value;
                if (v < 0)
                {
                    v = 0;
                }
                if (v > 999)
                {
                    v = 999;
                }
                RegisterChangeEventArgs e = new RegisterChangeEventArgs (_InstructionRegister, v);
                OnInstructionRegisterChanging (e);
                _InstructionRegister = v;
                OnInstructionRegisterChanged (e);
            }
        }

        #endregion

        #region TempValue

        public event EventHandler<RegisterChangeEventArgs> TempValueChanging;
        public event EventHandler<RegisterChangeEventArgs> TempValueChanged;

        protected virtual void OnTempValueChanging (RegisterChangeEventArgs e)
        {
            if (TempValueChanging != null)
            {
                TempValueChanging (this, e);
            }
        }

        protected virtual void OnTempValueChanged (RegisterChangeEventArgs e)
        {
            if (TempValueChanged != null)
            {
                TempValueChanged (this, e);
            }
        }

        public int TempValue
        {
            get
            {
                return _TempValue;
            }
            set
            {
                int v = value;
                if (v < 0)
                {
                    v = 0;
                }
                if (v > 9999)
                {
                    v = 9999;
                }
                RegisterChangeEventArgs e = new RegisterChangeEventArgs (_TempValue, v);
                OnTempValueChanging (e);
                _TempValue = v;
                OnTempValueChanged (e);
            }
        }

        #endregion

        #region AluResult

        public event EventHandler<RegisterChangeEventArgs> AluResultChanging;
        public event EventHandler<RegisterChangeEventArgs> AluResultChanged;

        protected virtual void OnAluResultChanging (RegisterChangeEventArgs e)
        {
            if (AluResultChanging != null)
            {
                AluResultChanging (this, e);
            }
        }

        protected virtual void OnAluResultChanged (RegisterChangeEventArgs e)
        {
            if (AluResultChanged != null)
            {
                AluResultChanged (this, e);
            }
        }

        public int AluResult
        {
            get
            {
                return _AluResult;
            }
            set
            {
                int v = value % 10000;

                if (_AluResult != v)
                {
                    RegisterChangeEventArgs e = new RegisterChangeEventArgs (_AluResult, v);
                    OnAluResultChanging (e);
                    _AluResult = v;
                    OnAluResultChanged (e);
                }
            }
        }

        #endregion

        #region Ready

        public event EventHandler ReadyChanged;

        public bool Ready
        {
            get
            {
                return _Ready;
            }
            set
            {
                if (_Ready != value)
                {
                    _Ready = value;
                    OnReadyChanged (EventArgs.Empty);
                }
            }
        }

        protected virtual void OnReadyChanged (EventArgs e)
        {
            if (ReadyChanged != null)
            {
                ReadyChanged (this, e);
            }
        }

        private void ChangeReadiness ()
        {
            Ready = _CurStep != null && !_Executing;
        }

        #endregion

        #region Memory Pointer

        /// <summary>
        ///     The contents of the memory cell that is addressed by the current instruction.
        /// </summary>

        public int PointedMemory
        {
            get
            {
                return _Memory[_MemoryPointer];
            }
            set
            {
                _Memory[_MemoryPointer] = value;
            }
        }

        #endregion

        #region Execution

        public void Startup ()
        {
            Accumulator = 0;
            ProgramCounter = 1;
            Animate (NoAnimate, StepFetch);
        }

        /// <summary>
        ///     Do an Inital Program Loading on the core.
        /// </summary>
        /// <remarks><para>
        ///     By design, an IPL is done by starting the execution at memory
        ///     location 0, which contains an "INP 01" instruction.
        /// </para><para>
        ///     An IPL expects the input tape to contain a loadable program.
        ///     Loadable programs start with the magic numbers 002, 800
        ///     as described in the CARDIAC user manual.
        /// </para></remarks>

        public void Ipl ()
        {
            Accumulator = 0;
            ProgramCounter = 0;
            Animate (NoAnimate, StepFetch);
        }

        /// <summary>
        ///     Execute as many clock cycles as possible
        /// </summary>

        public void ExecuteManySteps (int aMax)
        {
            lock (this)
            {
                _Executing = true;
                try
                {
                    while (_CurStep != null && aMax-- > 0)
                    {
                        var s = _CurStep;
                        _CurStep = null;
                        s ();
                    }
                }
                finally
                {
                    _Executing = false;
                    ChangeReadiness ();
                }
            }
        }

        public void ExecuteManySteps ()
        {
            ExecuteManySteps (1000);
        }

        public void ExecuteOneStep ()
        {
            ExecuteManySteps (_ClockMultiplier);
        }

        public int ClockMultiplier
        {
            get
            {
                return _ClockMultiplier;
            }
            set
            {
                _ClockMultiplier = value;
            }
        }

        #endregion

        #region Animation

        private void OnAnimationDone ()
        {
            lock (this)
            {
                _CurStep = _NextStep;
                ChangeReadiness ();
            }
        }

        /// <summary>
        ///     Executes an animation for the current clock cycle,
        ///     and schedules the operation to be performed in the
        ///     next clock cycle
        /// </summary>
        /// <param name="aNextStep">
        ///     The next step to perform when animation for current
        ///     step is done.
        /// </param>
        /// <param name="aAnimation">
        ///     The animation to perform for the current step.
        /// </param>
        /// <remarks><para>
        ///     Animations typically occur asynchronously, in a separate
        ///     thread, unless they are very short.
        /// </para><para>
        ///     The animation must call the delegate when it is complete,
        ///     otherwise execution of the next step will never occur.
        /// </para></remarks>

        private void Animate (Animation aAnimation, Step aNextStep)
        {
            _NextStep = aNextStep;
            aAnimation (OnAnimationDone);
        }

        private void NoAnimate (AnimationDone aNext)
        {
            aNext ();
        }

        private void AnimateLastStep (Animation aAnimation)
        {
            Animate (aAnimation, StepFetch);
        }

        #endregion

        #region Microcode

        private void StepFetch ()
        {
            InstructionRegister = _Memory[_ProgramCounter];
            Animate (_Animator.MemoryToInstruction, StepIncrementProgramCounter);
        }

        private void StepIncrementProgramCounter ()
        {
            ProgramCounter = ProgramCounter + 1;
            Animate (NoAnimate, StepDecodeInstruction);
        }

        private void StepCla ()
        {
            Accumulator = PointedMemory;
            AnimateLastStep (_Animator.MemoryToAccumulator);
        }

        private void StepSto ()
        {
            PointedMemory = Accumulator;
            AnimateLastStep (_Animator.AccumulatorToMemory);
        }

        private void StepAddLoadTmp ()
        {
            TempValue = PointedMemory;
            Animate (_Animator.MemoryToTemp, StepAdd);
        }

        private void StepAdd ()
        {
            AluResult = Accumulator + TempValue;
            Animate (_Animator.Add, StepAluToAccumulator);
        }

        private void StepAluToAccumulator ()
        {
            Accumulator = AluResult;
            AnimateLastStep (_Animator.ResultToAccumulator);
        }

        private void StepSubLoadTmp ()
        {
            TempValue = PointedMemory;
            Animate (_Animator.MemoryToTemp, StepSub);
        }

        private void StepSub ()
        {
            AluResult = Accumulator - TempValue;
            Animate (_Animator.Sub, StepAluToAccumulator);
        }

        private void StepJmp ()
        {
            // Should animate the store to 99
            _Memory[99] = Instruction.Jmp (ProgramCounter);

            ProgramCounter = _MemoryPointer;
            AnimateLastStep (_Animator.PointerToProgramCounter);
        }

        private void StepInp ()
        {
            _WaitInput = true;
            if (ReadInput != null)
            {
                ReadInput (this, EventArgs.Empty);
            }
        }

        private void StepInpCopy ()
        {
            PointedMemory = InputBuffer;
            AnimateLastStep (NoAnimate);
        }

        private void StepDecodeInstruction ()
        {
            Instruction.Split (_InstructionRegister, out _OpCode, out _MemoryPointer);

            // TODO : Strictly speaking, there should be a distinct step between
            //        SplitInstruction and DecodeInstruction.

            switch (_OpCode)
            {
                case OpCode.ADD:
                    Animate (_Animator.SplitInstruction, StepAddLoadTmp);
                    break;

                case OpCode.CLA:
                    Animate (_Animator.SplitInstruction, StepCla);
                    break;

                case OpCode.HRS:
                    break;

                case OpCode.INP:
                    Animate (NoAnimate, StepInp);
                    break;

                case OpCode.JMP:
                    Animate (_Animator.SplitInstruction, StepJmp);
                    break;

                case OpCode.OUT:
                    break;

                case OpCode.SFT:
                    break;

                case OpCode.STO:
                    Animate (_Animator.SplitInstruction, StepSto);
                    break;

                case OpCode.SUB:
                    Animate (_Animator.SplitInstruction, StepSubLoadTmp);
                    break;

                case OpCode.TAC:
                    break;
            }
        }
        #endregion

        #region Input

        private bool _WaitInput;
        public int InputBuffer
        {
            get;
            private set;
        }

        /// <summary>
        ///     Event raised when the processor needs to read data from
        ///     the input tape.
        /// </summary>
        /// <remarks><para>
        ///     The environment should read one single value from the input tape,
        ///     and feed it to the processor with the ProcessInput() function.
        /// </para></remarks>

        public event EventHandler ReadInput;

        /// <summary>
        ///     Process data from the input tape.
        /// </summary>
        /// <param name="aInputValue">
        ///   The data read from the input tape.
        /// </param>
        /// <remarks><para>
        ///     This function should be called exactly once every time the
        ///     ReadInput event has been triggered. All other calls will be
        ///     silently ignored.
        /// </para></remarks>

        public void ProcessInput (int aInputValue)
        {
            if (_WaitInput)
            {
                _WaitInput = false;
                InputBuffer = aInputValue;
                Animate (NoAnimate, StepInpCopy);
            }
        }

        #endregion


    }
}
